]>
Commit | Line | Data |
---|---|---|
01801559 | 1 | /****************************************************************************** |
2 | * Copyright © 2014-2018 The SuperNET Developers. * | |
3 | * * | |
4 | * See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at * | |
5 | * the top-level directory of this distribution for the individual copyright * | |
6 | * holder information and the developer policies on copyright and licensing. * | |
7 | * * | |
8 | * Unless otherwise agreed in a custom licensing agreement, no part of the * | |
9 | * SuperNET software, including this file may be copied, modified, propagated * | |
10 | * or distributed except according to the terms contained in the LICENSE file * | |
11 | * * | |
12 | * Removal or modification of this copyright notice is prohibited. * | |
13 | * * | |
14 | ******************************************************************************/ | |
15 | How to write utxo based CryptoConditions contracts for KMD chains | |
16 | by jl777 | |
17 | ||
18 | This is not the only smart contracts methodology that is possible to build on top of OP_CHECKCRYPTOCONDITION, just the first one. All the credit for getting OP_CHECKCRYPTOCONDITION working in the Komodo codebase goes to @libscott. I am just hooking into the code that he made and tried to make it just a little easier to make new contracts. | |
19 | ||
20 | There is probably some fancy marketing name to use, but for now, I will just call it "CC contract" for short, knowing that it is not 100% technically accurate as the CryptoConditions aspect is not really the main attribute. However, the KMD contracts were built to make the CryptoConditions codebase that was integrated into it to be more accessible. | |
21 | ||
22 | Since CC contracts run native C/C++ code, it is turing complete and that means that any contract that is possible to do on any other platform will be possible to create via CC contract. | |
23 | ||
24 | utxo based contracts are a bit harder to start writing than for balance based contracts. However, they are much more secure as they leverage the existing bitcoin utxo system. That makes it much harder to have bugs that issue a zillion new coins from a bug, since all the CC contract operations needs to also obey the existing bitcoin utxo protocol. | |
25 | ||
26 | This document will be heavily example based so it will utilize many of the existing reference CC contracts. After understanding this document, you should be in a good position to start creating either a new CC contract to be integrated into komodod or to make rpc based dapps directly. | |
27 | ||
28 | Chapter 0 - Bitcoin Protocol Basics | |
29 | There are many aspects of the bitcoin protocol that isnt needed to understand the CC contracts dependence on it. Such details will not be discussed. The primary aspect is the utxo, unspent transaction output. Just a fancy name for txid/vout, so when you sendtoaddress some coins, it creates a txid and the first output is vout.0, combine it and txid/0 is a specific utxo. | |
30 | ||
31 | Of course, to understand even this level of detail requires that you understand what a txid is, but there are plenty of reference materials on that. It is basically the 64 char long set of letters and numbers that you get when you send funds. | |
32 | ||
33 | Implicit with the utxo is that it prevents double spends. Once you spend a utxo, you cant spend it again. This is quite an important characteristic and while advanced readers will point out chain reorgs can allow a double spend, we will not confuse the issue with such details. The important thing is that given a blockchain at a specific height's blockhash, you can know if a txid/vout has been spent or not. | |
34 | ||
35 | There are also the transactions that are in memory waiting to be mined, the mempool. And it is possible for the utxo to be spent by a tx in the mempool. However since it isnt confirmed yet, it is still unspent at the current height, even if we are pretty sure it will be spent in the next block. | |
36 | ||
37 | A useful example is to think about a queue of people lined up to get into an event. They need to have a valid ticket and also to get into the queue. After some time passes, they get their ticket stamped and allowed into the event. | |
38 | ||
39 | In the utxo case, the ticket is the spending transaction and the event is the confirmed blockchain. The queue is the mempool. | |
40 | ||
41 | ||
42 | Chapter 1 - OP_CHECKCRYPTOCONDITION | |
43 | In the prior chapter the utxo was explained. However, the specific mechanism used to send a payment was not explained. Contrary to what most people might think, on the blockchain there are not entries that say "pay X amount to address". Instead what exists is a bitcoin script that must be satisfied in order for the funds to be able to be spent. | |
44 | ||
45 | Originally, there was the pay to pubkey script: | |
46 | <pubkey> <checksig> | |
47 | ||
48 | About as simple of a payment script that you can get. Basically the pubkey's signature is checked and if it is valid, you get to spend it. Once problem satoshi realized was that with Quantum Computers such payment scripts are vulnerable! So, he made a way to have a cold address, ie. an address whose pubkey isnt known. At least it isnt known until it is spent, so it is only Quantum resistant prior to the first spend. This line of reasoning is why we have one time use addresses and a new change address for each transaction. Maybe in some ways, this is too forward thinking as it makes things a lot more confusing to use and easier to lose track of all the required private keys. | |
49 | ||
50 | However, it is here to stay and its script is: | |
51 | <hash the pubkey> <pubkey> <verify hash matches> <checksig> | |
52 | ||
53 | With this, the blockchain has what maps to "pay to address", just that the address is actually a base58 encoded (prefix + pubkeyhash). Hey, if it wasnt complicated, it would be easy! | |
54 | ||
55 | In order to spend a p2pkh (pay to pubkey hash) utxo, you need to divulge the pubkey in addition to having a valid signature. After the first spend from an address, its security is degraded to p2pk (pay to pubkey) as its pubkey is now known. The net result is that each reused address takes 25 extra bytes on the blockchain, and that is why for addresses that are expected to be reused, I just use the p2pk script. | |
56 | ||
57 | Originally, bitcoin allowed any type of script opcodes to be used directly. The problem was some of them caused problems and satoshi decided to disable them and only allow standard forms of payments. Thus the p2pk and p2pkh became 99%+ of bitcoin transactions. However, going from having a fully scriptable language that can create countless payment scripts (and bugs!), to having just 2... well it was a "short term" limitation. It did last for some years but eventually a compromise p2sh script was allowed to be standard. This is a pay to script hash, so it can have a standard format as the normal p2pkh, but have infinitely more flexibility. | |
58 | ||
59 | <hash the script> <script> <verify hash matches> | |
60 | ||
61 | Wait, something is wrong! If it was just that, then anybody that found out what the required script (called redeemscript) was, they could just spend it. I forgot to say that the redeemscript is then used to determine if the payment can be spent or not. So you can have a normal p2pk or p2pkh redeemscript inside a p2sh script. | |
62 | ||
63 | OK, I know that just got really confusing. Let us have a more clear example: | |
64 | ||
65 | redeemscript <- pay to pubkey | |
66 | p2sh becomes the hash of the redeem script + the compares | |
67 | ||
68 | So to spend it, you need to divulge the redeemscript, which in turn requires you to divulge the pubkey. Put it all together and the p2sh mechanism verifies you not only had the correct redeemscript by comparing its hash, but that when the redeemscript is run, it is satisfied. In this case, that the pubkey's signature was valid. | |
69 | ||
70 | If you are still following, there is some good news! OP_CHECKCRYPTOCONDITION scripts are actually simpler than p2sh scripts in some sense as there isnt this extra level of script inside a scripthash. @libscott implemented the addition of OP_CHECKCRYPTOCONDITION to the set of bitcoin opcodes and what it does is makes sure that a CryptoConditions script is properly signed. | |
71 | ||
72 | Which gets us to the CryptoConditions specification, which is a monster of a IETF (Internet standards) draft and has hundred(s) of pages of specification. I am sure you are happy to know that you dont really need to know about it much at all! Just know that you can create all sorts of cryptoconditions and its binary encoding can be used in a bitcoin utxo. If the standard CC contracts dont have the power you need, it is always possible to expand on it. So far, most all the CC contracts only need the power of a 1of1 CC script, which is 1 signature combined with custom constraints. The realtime payment channels CC is the only one of the reference CC contracts so far that didnt fit into this model, it needed a 1of2 CC script. | |
73 | ||
74 | The best part is that all these opcode level things are not needed at all. I just wanted to explain it for those that need to know all the details of everything. | |
75 | ||
76 | Chapter 2 - CC contract basics | |
77 | Each CC contract has an eval code, this is just an arbitrary number that is associated with a specific CC contract. The details about a specific CC contract are all determined by the validation logic, that is ultimately what implements a CC contract. | |
78 | ||
79 | However, unlike the normal bitcoin payments, where it is validated with only information in the transaction, a CC contract has the power to do pretty much anything. It has full access to the blockchain and even the mempool, though using mempool information is inherently more risky and needs to be done carefully or for exclusions, rather than inclusions. | |
80 | ||
81 | However, this is the CC contract basics chapter, so let us ignore mempool issues and deal with just the basics. Fundamentally there is no structure for OP_CHECKCRYPTOCONDITION serialized scripts, but if you are like me, you want to avoid having to read and understand a 1000 page IETF standard. What we really want to do is have a logical way to make a new contract and have it be able to be coded and debugged in an efficient way. | |
82 | ||
83 | That means to just follow a known working template and only changing the things where the existing templates are not sufficient, ie. the core differentiator of your CC contract. | |
84 | ||
85 | In the ~/komodo/src/cc/eval.h file all the eval codes are defined, currently: | |
86 | ||
87 | #define FOREACH_EVAL(EVAL) \ | |
88 | EVAL(EVAL_IMPORTPAYOUT, 0xe1) \ | |
89 | EVAL(EVAL_IMPORTCOIN, 0xe2) \ | |
90 | EVAL(EVAL_ASSETS, 0xe3) \ | |
91 | EVAL(EVAL_FAUCET, 0xe4) \ | |
92 | EVAL(EVAL_REWARDS, 0xe5) \ | |
93 | EVAL(EVAL_DICE, 0xe6) \ | |
94 | EVAL(EVAL_FSM, 0xe7) \ | |
95 | EVAL(EVAL_AUCTION, 0xe8) \ | |
96 | EVAL(EVAL_LOTTO, 0xe9) \ | |
97 | EVAL(EVAL_MOFN, 0xea) \ | |
98 | EVAL(EVAL_CHANNELS, 0xeb) \ | |
99 | EVAL(EVAL_ORACLES, 0xec) \ | |
100 | EVAL(EVAL_PRICES, 0xed) \ | |
101 | EVAL(EVAL_PEGS, 0xee) \ | |
102 | EVAL(EVAL_TRIGGERS, 0xef) \ | |
103 | EVAL(EVAL_PAYMENTS, 0xf0) \ | |
104 | EVAL(EVAL_GATEWAYS, 0xf1) | |
105 | ||
106 | Ultimately, we will probably end up with all 256 eval codes used, for now there is plenty of room. I imagined that similar to my coins repo, we can end up with a much larger than 256 number of CC contracts and you select the 256 that you want active for your blockchain. That does mean any specific chain will be limited to "only" having 256 contracts. Since there seems to be so few actually useful contracts so far, this limit seems to be sufficient. I am told that the evalcode can be of any length, but the current CC contracts assumes it is one byte. | |
107 | ||
108 | The simplest CC script would be one that requires a signature from a pubkey along with a CC validation. This is the equivalent of the pay to pubkey bitcoin script and is what most of the initial CC contracts use. Only the channels one needed more than this and it will be explained in its chapter. | |
109 | ||
110 | We end up with CC scripts of the form (evalcode) + (pubkey) + (other stuff), dont worry about the other stuff, it is automatically handled with some handy internal functions. The important thing to note is that each CC contract of this form needs a single pubkey and eval code and from that we get the CC script. Using the standard bitcoin's "hash and make an address from it" method, this means that the same pubkey will generate a different address for each different CC contract! | |
111 | ||
112 | This is an important point, so I will say it in a different way. In bitcoin there used to be uncompressed pubkeys which had both the right and left half combined, into a giant 64 byte pubkey. But since you can derive one from the other, compressed pubkeys became the standard, that is why you have bitcoin pubkeys of 33 bytes instead of 65 bytes. There is a 02, 03 or 04 prefix, to mean odd or even or big pubkey. This means there are two different pubkeys for each privkey, the compressed and uncompressed. And in fact you can have two different bitcoin protocol addresses that are spendable by the same privkey. If you use some paper wallet generators, you might have noticed this. | |
113 | ||
114 | CC contracts are like that, where each pubkey gets a different address for each evalcode. It is the same pubkey, just different address due to the actual script having a different evalcode, it ends up with a different hash and thus a different address. Now funds send to a specific CC address is only accessible by that CC contract and must follow the rules of that contract. | |
115 | ||
116 | I also added another very useful feature where the convention is for each CC contract to have a special address that is known to all, including its private key. Before you panic about publishing the private key, remember that to spend a CC output, you need to properly sign it AND satisfy all the rules. By everyone having the privkey for the CC contract, everybody can do the "properly sign" part, but they still need to follow the rest of the rules. | |
117 | ||
118 | From a user's perspective, there is the global CC address for a CC contract and some contracts also use the user pubkey's CC address. Having a pair of new addresses for each contract can get a bit confusing at first, but eventually we will get easy to use GUI that will make it all easy to use. | |
119 | ||
120 | ||
121 | Chapter 3 - CC vins and vouts | |
122 | You might want to review the bitcoin basics and other materials to refresh about how bitcoin outputs become inputs. It is a bit complicated, but ultimately it is about one specific amount of coins that are spent, once spent it is combined with the other coins that are also spent in that transaction and then various outputs are created. | |
123 | ||
124 | vin0 + vin1 + vin2 -> vout0 + vout1 | |
125 | ||
126 | That is a 3 input, 2 output transaction. The value from the three inputs are combined and then split into vout0 and vout1, each of the vouts gets a spend script that must be satisfied to be able to be spent. Which means for all three of out vins, all the requirements (as specified in the output that created them) are satisfied. | |
127 | ||
128 | Yes, I know this is a bit too complicated without a nice chart, so we will hope that a nice chart is added here: | |
129 | ||
130 | [nice chart goes here] | |
131 | ||
132 | Out of all the aspects of the CC contracts, the flexibility that different vins and vouts created was the biggest surprise. When I started writing the first of these a month ago, I had no idea the power inherent in the smart utxo contracts. I was just happy to have a way to lock funds and release them upon some specific conditions. | |
133 | ||
134 | After the assets/tokens CC contract, I realized that it was just a tip of the iceberg. I knew it was Turing complete, but after all these years of restricted bitcoin script, to have the full power of any arbitrary algorithm, it was eye opening. Years of writing blockchain code and having really bad consequences with every bug naturally makes you gun shy about doing aggressive things at the consensus level. And that is the way it should be, if not very careful, some really bad things can and do happen. The foundation of building on top of the existing (well tested and reliable) utxo system is what makes the CC contracts less likely for the monster bugs. That being said, lack of validation can easily allow an improperly coded CC contract to have its funds drained. | |
135 | ||
136 | The CC contract breaks out of the standard limitations of a bitcoin transaction. Already, what I wrote explains the reason, but it was not obvious even to me at first, so likely you might have missed it too. If you are wondering what on earth I am talking about, THAT is what I am talking about! | |
137 | ||
138 | To recap, we have now a new standard bitcoin output type called a CC output. Further, there can be up to 256 different types of CC outputs active on any given blockchain. We also know that to spend any output, you need to satisfy its spending script, which in our case is the signature and whatever constraints the CC validation imposes. We also have the convention of a globally shared keypair, which gives us a general CC address that can have funds sent to it, along with a user pubkey specific CC address. | |
139 | ||
140 | Let us go back to the 3+2 transaction example: | |
141 | ||
142 | vin0 + vin1 + vin2 -> vout0 + vout1 | |
143 | ||
144 | Given the prior paragraph, try to imagine the possibilities the simple 3+2 transaction can be. Each vin could be a normal vin, from the global contract address, the user's CC address and the vouts can also have this range. Theoretically, there can be 257 * 257 * 257 * 257 * 257 forms of a 3+2 transaction! | |
145 | ||
146 | In reality, we really dont want that much degrees of freedom as it will ensure a large degree of bugs! So we need to reduce things to a more manageable level where there are at most 3 types for each, and preferably just 1 type. That will make the job of validating it much simpler and simple is better as long as we dont sacrifice the power. We dont. | |
147 | ||
148 | Ultimately the CC contract is all about how it constrains its inputs, but before it can constrain them, they need to be created as outputs. More about this in the CC validation chapter. | |
149 | ||
150 | Chapter 4 - CC rpc extensions | |
151 | Currently, CC contracts need to be integrated at the source level. This limits who is able to create and add new CC contracts, which at first is good, but eventually will be a too strict limitation. The runtime bindings chapter will touch on how to break out of the source based limitation, but there is another key interface level, the RPC. | |
152 | ||
153 | By convention, each CC contract adds an associated set of rpc calls to the komodo-cli. This not only simplifies the creation of the CC contract transactions, it further will allow dapps to be created just via rpc calls. That will require there being enough foundational CC contracts already in place. As we find new usecases that cannot be implemented via rpc, then a new CC contract is made that can handle that (and more) and the power of the rpc level increases. This is a long term process. | |
154 | ||
155 | The typical rpc calls that are added <CC>address, <CClist>, <CCinfo> return the various special CC addresses, the list of CC contract instances and info about each CC contract instance. Along with an rpc that creates a CC instance and of course the calls to invoke a CC instance. | |
156 | ||
157 | The role of the rpc calls are to create properly signed rawtransactions that are ready for broadcasting. This then allows using only the rpc calls to not only invoke but to create a specific instance of a CC. The faucet contract is special in that it only has a single instance, so some of these rpc calls are skipped. | |
158 | ||
159 | So, there is no MUSTHAVE rpc calls, just a sane convention to follow so it fits into the general pattern. | |
160 | ||
161 | One thing that I forgot to describe was how to create a special CC address and even though this is not really an rpc issue, it is kind of separate from the core CC functions, so I will show how to do it here: | |
162 | ||
163 | const char *FaucetCCaddr = "R9zHrofhRbub7ER77B7NrVch3A63R39GuC"; | |
164 | const char *FaucetNormaladdr = "RKQV4oYs4rvxAWx1J43VnT73rSTVtUeckk"; | |
165 | char FaucetCChexstr[67] = { "03682b255c40d0cde8faee381a1a50bbb89980ff24539cb8518e294d3a63cefe12" }; | |
166 | uint8_t FaucetCCpriv[32] = { 0xd4, 0x4f, 0xf2, 0x31, 0x71, 0x7d, 0x28, 0x02, 0x4b, 0xc7, 0xdd, 0x71, 0xa0, 0x39, 0xc4, 0xbe, 0x1a, 0xfe, 0xeb, 0xc2, 0x46, 0xda, 0x76, 0xf8, 0x07, 0x53, 0x3d, 0x96, 0xb4, 0xca, 0xa0, 0xe9 }; | |
167 | ||
168 | Above are the specifics for the faucet CC, but each one has the equivalent in CCcustom.cpp. At the bottom of the file is a big switch statement where these values are copied into an in memory data structure for each CC type. This allows all the CC codebase to access these special addresses in a standard way. | |
169 | ||
170 | In order to get the above values, follow these steps: | |
171 | A. use getnewaddress to get a new address and put that in the <CC>Normaladdr = ""; line | |
172 | B. use validateaddress <newaddress from A> to get the pubkey, which is put into the <CC>hexstr[67] = ""; line | |
173 | C. stop the daemon and start with -pubkey=<pubkey from B> and do a <CC>address rpc call. In the console you will get a printout of the hex for the privkey, assuming the if ( 0 ) in Myprivkey() is enabled (CCutils.cpp) | |
174 | D. update the CCaddress and privkey and dont forget to change the -pubkey= parameter | |
175 | ||
176 | The first rpc command to add is <CC>address and to do that, add a line to rpcserver.h and update the commands array in rpcserver.cpp | |
177 | ||
178 | In the rpcwallet.cpp file you will find the actual rpc functions, find one of the <CC>address ones, copy paste, change the eval code to your eval code and customize the function. Oh, and dont forget to add an entry into eval.h | |
179 | ||
180 | Now you have made your own CC contract, but it wont link as you still need to implement the actual functions of it. This will be covered in the following chapters. | |
181 | ||
182 | ||
183 | Chapter 5 - CC validation | |
184 | CC validation is what its all about, not the "hokey pokey"! | |
185 | ||
186 | Each CC must have its own validation function and when the blockchain is validating a transaction, it will call the CC validation code. It is totally up to the CC validation whether to validate it or not. | |
187 | ||
188 | Any set of rules that you can think of and implement can be part of the validation. Make sure that there is no ambiguity! Make sure that all transactions that should be rejected are in fact rejected. | |
189 | ||
190 | Also, make sure any rpc calls that create a CC transaction dont create anything that doesnt validate. | |
191 | ||
192 | Really, that is all that needs to be said about validation that is generic, as it is just a concept and gets a dedicated function to determine if a transaction is valid or not. | |
193 | ||
194 | For most of the initial CC contracts, I made a function code for various functions of the CC contract and add that along with the creation txid. That enables the validation of the transactions much easier, as the required data is right there in the opreturn. | |
195 | ||
196 | You do need to be careful not to cause a deadlock as the CC validation code is called while already locked in the main loop of the bitcoin protocol. As long as the provided CC contracts are used as models, you should keep out of deadlock troubles. | |
197 | ||
198 | ||
199 | Chapter 6 - faucet example | |
200 | Finally, we are ready for the first actual example of a CC contract. The faucet. This is a very simple contract and it ran into some interesting bugs in the first incarnation. | |
201 | ||
202 | The code in ~/komodo/src/cc/faucet.cpp is the ultimate documentation for it with all the details, so I will just address the conceptual issues here. | |
203 | ||
204 | There are only 7 functions in faucet.cpp, a bit over 200 lines including comments. The first three are for validation, the last four for the rpc calls to use. | |
205 | ||
206 | int64_t IsFaucetvout(struct CCcontract_info *cp,const CTransaction& tx,int32_t v) | |
207 | ||
208 | bool FaucetExactAmounts(struct CCcontract_info *cp,Eval* eval,const CTransaction &tx,int32_t minage,uint64_t txfee) | |
209 | ||
210 | bool FaucetValidate(struct CCcontract_info *cp,Eval* eval,const CTransaction &tx) | |
211 | ||
212 | int64_t AddFaucetInputs(struct CCcontract_info *cp,CMutableTransaction &mtx,CPubKey pk,int64_t total,int32_t maxinputs) | |
213 | ||
214 | std::string FaucetGet(uint64_t txfee) | |
215 | ||
216 | std::string FaucetFund(uint64_t txfee,int64_t funds) | |
217 | ||
218 | UniValue FaucetInfo() | |
219 | ||
220 | Functions in rpcwallet implement: | |
221 | ||
222 | faucetaddress fully implemented in rpcwallet.cpp | |
223 | faucetfund calls FaucetFund | |
224 | faucetget calls FaucetGet | |
225 | faucetinfo calls FaucetInfo | |
226 | ||
227 | Now you might not be a programmer, but I hope you are able to understand the above sequence. user types in a cli call, komodo-cli processes it by calling the rpc function, which in turn calls the function inside faucet.cpp | |
228 | ||
229 | No magic, just simple conversion of a user command line call that runs code inside the komodod. Both the faucetfund and faucetget create properly signed rawtransaction that is ready to be broadcast to the network using the standard sendrawtransaction rpc. It doesnt automatically do this to allow the GUI to have a confirmation step with all the details before doing an irrevocable CC contract transaction. | |
230 | ||
231 | faucetfund allows anybody to add funds to the faucet | |
232 | faucetget allows anybody to get 0.1 coins from the faucet as long as they dont violate the rules. | |
233 | ||
234 | And we come to what it is all about. The rules of the faucet. Initially it was much less strict and that allowed it to be drained slowly, but automatically and it prevented most from being able to use the faucet. | |
235 | ||
236 | To make it much harder to leech, it was made so each faucetget returned only 0.1 coins (down from 1.0) so it was worth 90% less. It was also made so that it had to be to a fresh address with less than 3 transactions. Finally each txid was constrained to start and end with 00! This is a cool trick to force usage of precious CPU time (20 to 60 seconds depending on system) to generate a valid txid. Like PoW mining for the txid and I expect other CC contracts to use a similar mechanism if they want to rate limit usage. | |
237 | ||
238 | Combined, it became such a pain to get 0.1 coins, the faucet leeching problem was solved. It might not seem like too much trouble to change an address to get another 0.1 coins, but the way things are setup you need to launch the komodod -pubkey=<your pubkey> to change the pubkey that is active for a node. That means to change the pubkey being used, the komodod needs to be restarted and this creates a lot of issues for any automation trying to do this. Combined with the PoW required, only when 0.1 coins becomes worth a significant effort will faucet leeching return. In that case, the PoW requirement can be increased and coin amount decreased, likely with a faucet2 CC contract as I dont expect many such variations to be needed. | |
239 | ||
240 | Chapter 7 - rewards example | |
241 | ||
242 | Chapter 8 - assets example | |
243 | Chapter 9 - dice example | |
244 | Chapter 10 - lotto example | |
245 | Chapter 11 - channels example | |
246 | Chapter 12 - limitless possibilities | |
247 | ||
248 | Chapter 13 - different languages | |
249 | Chapter 14 - runtime bindings | |
250 | Chapter 15 - rpc based dapps | |
251 | ||
252 | ||
253 | ||
254 |